/**
 * 1. make
 * 2. ./mpp-dec-h264-to-yuv-file
 * 3. gst-launch-1.0 filesrc location=Tennis1080p.yuv ! videoparse width=1920 height=1080 format=nv12 ! videoconvert ! xvimagesink
 * 4. gst-launch-1.0 filesrc location=Tennis1080p.h264 ! h264parse ! mppvideodec ! xvimagesink
 */
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <rockchip/rk_mpi.h>

#define __IN_FILE__ ("01.h265")
#define __OUT_FILE__ ("01.yuv")

int dump_frame(MppFrame frame, int *width, int *height, int *h_stride, int *v_stride)
{
    //printf("dump_frame_to_file\n");

    MppFrameFormat fmt  = MPP_FMT_YUV420SP;
    MppBuffer buffer    = NULL;
    RK_U8 *base = NULL;

    *width    = mpp_frame_get_width(frame);
    *height   = mpp_frame_get_height(frame);
    *h_stride = mpp_frame_get_hor_stride(frame);
    *v_stride = mpp_frame_get_ver_stride(frame);
    fmt      = mpp_frame_get_fmt(frame);
    buffer   = mpp_frame_get_buffer(frame);

    RK_U32 buf_size = mpp_frame_get_buf_size(frame);
    //printf("w x h: %dx%d hor_stride:%d ver_stride:%d buf_size:%d\n",width, height, h_stride, v_stride, buf_size);
           
    if (NULL == buffer) {
        //printf("buffer is null\n");
        return -1;
    }

    base = (RK_U8 *)mpp_buffer_get_ptr(buffer);

    // MPP_FMT_YUV420SP
    if (fmt != MPP_FMT_YUV420SP) {
        //printf("fmt %d not supported\n", fmt);
        return -1;
    }
    
    return 0;
}

int main(int argc, char *argv[])
{
    //printf("---------- mpp start ----------\n");
    if(argc != 2)
    {
        printf("usage:%s <.h265 file>\n", argv[0]);
        printf("return <width>, <height>, <h_stride>, <v_stride>\n");
    }

    // 1. 打开输入文件
    FILE *in_fp = fopen(argv[1], "rb");
    if (!in_fp) {
        //printf("fopen error\n");
        return -1;
    }

    // 3. 初始化解码器上下文，MppCtx MppApi
    MppCtx ctx = NULL;
    MppApi *mpi = NULL;
    MPP_RET ret = mpp_create(&ctx, &mpi);
    if (MPP_OK != ret) {
        //printf("mpp_create error\n");
        return -1;
    }

    /**
     * 4. 配置解器
     *      - 解码文件需要 split 模式
     *      - 设置非阻塞模式，0非阻塞（默认），-1阻塞，+val 超时(ms)
     */
    RK_U32 need_split = -1;
    ret = mpi->control(ctx, MPP_DEC_SET_PARSER_SPLIT_MODE, (MppParam*)&need_split);
    if (MPP_OK != ret) {
        //printf("mpi->control error MPP_DEC_SET_PARSER_SPLIT_MODE\n");
        return -1;
    }
    
    ret = mpp_init(ctx, MPP_CTX_DEC, MPP_VIDEO_CodingHEVC);  // 固定为H265 https://blog.csdn.net/weixin_38807927/article/details/135760601
    if (MPP_OK != ret) {
        //printf("mpp_init error\n");
        return -1;
    }

    // 5. 初始化包，MppPacket
    int buf_size = 5 * 1024 * 1024;
    char *buf = (char*)malloc(buf_size);
    if (!buf) {
        //printf("malloc error\n");
        return -1;
    }
    MppPacket pkt = NULL;
    ret = mpp_packet_init(&pkt, buf, buf_size);
    if (MPP_OK != ret) {
        //printf("mpp_packet_init error\n");
        return -1;
    }

    // 6. 循环读取文件，输入解码器，解码，保存结果
    int over = 0;
    while (!over) {
        //printf("decode...\n");
        int len = fread(buf, 1, buf_size, in_fp);
        //printf("read file length:%d\n", len);

        if (0 < len) {
            mpp_packet_write(pkt, 0, buf, len);
            mpp_packet_set_pos(pkt, buf);
            mpp_packet_set_length(pkt, len);
            if (feof(in_fp) || len < buf_size) {  // 文件读完，设置结束标志位
                mpp_packet_set_eos(pkt);
                //printf("mpp_packet_set_eos\n");
            }
        }

        /**
         * decode_put_packet返回失败，意味着内部缓冲区已满。
         * 非阻塞模式，使用pkt_is_send判断当前读取的数据包（buf）是否成功发送。
         */
        int pkt_is_send = 0;
        while (!pkt_is_send && !over) {
            if (0 < len) {
                //printf("pkt remain:%d\n", mpp_packet_get_length(pkt));
                ret = mpi->decode_put_packet(ctx, pkt);
                if (MPP_OK == ret) {
                    //printf("pkt send success remain:%d\n", mpp_packet_get_length(pkt));
                    pkt_is_send = 1;
                }
            }

            MppFrame frame;
            MPP_RET ret;
            ret = mpi->decode_get_frame(ctx, &frame);
            if (MPP_OK != ret || !frame) {
                //printf("decode_get_frame failed ret:%d\n", ret);
                usleep(2000);  // 等待一下2ms，通常1080p解码时间2ms
                continue;
            }

            //printf("decode_get_frame success\n");
            int width, height, h_stride, v_stride;
            dump_frame(frame, &width, &height, &h_stride, &v_stride);
            printf("%d, %d, %d, %d\n", width, height, h_stride, v_stride);
            exit(0);

            if (mpp_frame_get_eos(frame)) {
                //printf("mpp_frame_get_eos\n");
                mpp_frame_deinit(&frame);
                over = 1;
                continue;
            }
            mpp_frame_deinit(&frame);
        }
    }

    // 7. 释放资源
    fclose(in_fp);
    mpi->reset(ctx);
    mpp_packet_deinit(&pkt);
    mpp_destroy(ctx);
    free(buf);
    
    //printf("---------- mpp over ----------\n");
    return 0;
}
